/*
  This software is copyright 2009 Matthew Wilson matthew@wilson.org
  and available under the Artistic License 2 ("AL2") as documented at
  http://jsmeta.googlecode.com
*/

TestMode = true;

function runTests() {
  var startTime = (new Date()).getTime();
  
  // traceOn = typeof(callTraceNodeAdd)!='undefined' && debugMode;
  
  // traceOn = false;
  
  //_canLog = traceOn;
  
  var result = '';
  
  //if (typeof(console)!='undefined' && console.profile) { console.profile() }
  
  function testMGrammarGrammarAndInput(mGrammarGrammar, mGrammarInput, mGrammarOutput) {
    return {
      input: mGrammarInput
    , grammar: function(l) {
        inNestedParse = true;
        Module(function Microsoft__M__Grammar(l) {
          l.__Language(MGrammar).Export();
        });
        return Module.Exec(function runit(l) {
          l.__Import('Microsoft.M.Grammar');
          l.__Init(mGrammarGrammar);
          return [l.Microsoft.M.Grammar.MGrammar.Main(), l];
        });}
    , result: mGrammarOutput
    };
  }
  
  var tests =
  [ { input: "ii"
    , grammar: function(l) { return l.token(l.repeat(2,l.any())) }
    , result: "ii"}
  , { input: "iii"
    , grammar: function(l) { return l.token(l.repeat(2,l.any())) }
    , result: "Expected end() at line 1, column 3; instead found \"i\""}
  , { input: "iii"
    , grammar: function(l) { return l.token(l.seq(l.any(),l.any(),l.any())) }
    , result: "iii"}
  , { input: "ii"
    , grammar: function(l) { return l.token(l.either(l.repeat(3,l.any()),l.repeat(2,l.any()))) }
    , result: "ii"}
  , { input: "ii"
    , grammar: function(l) { return l.token(l.repeatBetween(1,2,l.any())) }
    , result: "ii"}
  , { input: "iii"
    , grammar: function(l) { return l.token(l.either(l.repeat(2,l.any()),l.repeatBetween(3,4,l.any()))) }
    , result: "iii"}
  , { input: "iiii"
    , grammar: function(l) { return l.token(l.repeatBetween(2,4,l.any())) }
    , result: "iiii"}
  , { input: "iiii"
    , grammar: function(l) { return l.token(l.repeatBetween(2,4,l.any())) }
    , result: "iiii"}
  , { input: "iii"
    , grammar: function(l) { return l.token(l.repeatBetween(2,4,l.any())) }
    , result: "iii"}
  , { input: "iii"
    , grammar: function(l) { return l.token(l.repeatBetween(0,5,l.any())) }
    , result: "iii"}
  , { input: "iii"
    , grammar: function(l) { return l.token(l.repeatBetween(0,3,l.any())) }
    , result: "iii"}
  , { input: "iii"
    , grammar: function(l) { return l.token(l.both(l.repeatBetween(2,4,l.any()),l.any())) }
    , result: "iii"}
  , { input: "iiii"
    , grammar: function(l) { return l.token(l.both(l.repeatBetween(2,4,l.any()),l.any())) }
    , result: "iiii"}
  , { input: "iiiii"
    , grammar: function(l) { return l.token(l.both(l.repeatBetween(2,4,l.any()),l.any())) }
    , result: "iiiii"}
  , { input: "iiiiii"
    , grammar: function(l) { return l.token(l.both(l.repeatBetween(2,4,l.any()),l.any())) }
    , result: "Expected end() at line 1, column 6; instead found \"i\""}
  , { input: "iiiiii"
    , grammar: function(l) { return l.token(l.both(l.repeatBetween(0,4,l.any()),l.any())) }
    , result: "Expected end() at line 1, column 6; instead found \"i\""}
  , { input: "iiiiii"
    , grammar: function(l) { return l.token(l.both(l.repeatBetween(0,5,l.any()),l.any())) }
    , result: "iiiiii"}
  , { input: "i"
    , grammar: function(l) { return l.token(l.both(l.repeatBetween(0,1,l.any()),l.any())) }
    , result: "i"}
  , { input: ""
    , grammar: function(l) { return l.token(l.any()) }
    , result: "Expected any at line 1, column 1; instead found the end of input"}
  , { input: ""
    , grammar: function(l) { return l.token(l.empty()) }
    , result: ""}
  , { input: "iii"
    , grammar: function(l) { return l.token(l.repeat(3,l.any('i'))) }
    , result: "iii"}
  , { input: "ziziz"
    , grammar: function(l) { return l.token(l.repeat(5,l.any('i','z'))) }
    , result: "ziziz"}
  , { input: "zizizzilzilizlilzilizlizl"
    , grammar: function(l) { return l.token(l.repeatBetween(0,50,l.any('i','z','l'))) }
    , result: "zizizzilzilizlilzilizlizl"}
  , { input: "zizizzilzilizlilzilizlizliiiiiiiiiiii"
    , grammar: function(l) { return l.token(l.repeatBetween(0,100,l.any('i','z','l'))) }
    , result: "zizizzilzilizlilzilizlizliiiiiiiiiiii"}
  , { input: "zzzfz"
    , grammar: function(l) { return l.token(l.repeat(5,l.none('z','l'))) }
    , result: "Expected none( \"z\", \"l\" ) at line 1, column 1; instead found \"zzzfz\""}
  , { input: "zzzfz"
    , grammar: function(l) { return l.token(l.repeat(5,l.none('q','r'))) }
    , result: "zzzfz"}
  , { input: "zzzfz"
    , grammar: function(l) { return l.token(l.repeat(5,l.none())) }
    , result: "zzzfz"}
  , { input: "fdfds"
    , grammar: function(l) { return l.token(l.repeatBetween(0,Infinity,l.any())) }
    , result: "fdfds"}
  , { input: "fdfds"
    , grammar: function(l) { return l.token(l.plus(l.any())) }
    , result: "fdfds"}
  , { input: ""
    , grammar: function(l) { return l.token(l.plus(l.any())) }
    , result: "Expected any at line 1, column 1; instead found the end of input"}
  , { input: ""
    , grammar: function(l) { return l.token(l.star(l.any())) }
    , result: ""}
  , { input: ""
    , grammar: function(l) { return l.token(l.not(l.any())) }
    , result: ""}
  , { input: "ac"
    , grammar: function(l) { return l.token(l.seq(l.any('a'),l.not(l.any('c')))) }
    , result: "Expected not( any( \"c\" ) ) at line 1, column 2; instead found \"c\""}
  , { input: "abc"
    , grammar: function(l) { return l.token(l.seq(l.any('a'),l.not(l.any('z')),l.any('bc'))) }
    , result: "abc"}
  , { input: "ac"
    , grammar: function(l) { return l.token(l.seq(l.any('a'),l.lookahead(l.any('b')),l.any('c'))) }
    , result: "Expected lookahead( any( \"b\" ) ) at line 1, column 2; instead found \"c\""}
  , { input: "ac"
    , grammar: function(l) { return l.token(l.seq(l.any('a'),l.lookahead(l.any('c')),l.any('c'))) }
    , result: "ac"}
  , { input: "aa"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function fooLR() { with (l) {
          return token(either(
            both( fooLR(), any('a') )
          , any('a')))}});
        return fooLR();
      }}
    , result: "aa"}
  , { input: "aa"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function fooLR() { with (l) {
          return token(either(
            both( fooLR(), any('b') )
          , any('b')))}});
        return fooLR();
      }}
    , result: "Expected fooLR() at line 1, column 1; instead found \"aa\""}
  , { input: "aaa"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function fooLR() { with (l) {
          return token(either(
            both( fooLR(), any('a') )
          , any('a')))}});
        return fooLR();
      }}
    , result: "aaa"}
  , { input: "aaaa"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function fooLR() { with (l) {
          return token(either(
            both( fooLR(), any('a') )
          , any('a')))}});
        return fooLR();
      }}
    , result: "aaaa"}
  , { input: "aaaab"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function fooLR() { with (l) {
          return token(either(
            both( fooLR(), either( any('a'), any('b') ) )
          , any('a')))}});
        return fooLR();
      }}
    , result: "aaaab"}
  , { input: "aaaab"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function fooLR() { with (l) {
          return token(alt(
            both( fooLR(), either( any('a'), any('b') ) )
          , any('a')
          , empty()))}});
        return fooLR();
      }}
    , result: "aaaab"}
  , { input: "aaaab"
    , todo: "not sure whether this is a degenerate grammar"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function fooLR() { with (l) {
          return token(alt(
            both( fooLR(), either( any('a'), any('b') ) )
          , empty()))}});
        return fooLR();
      }}
    , result: "aaaab"}
  , { input: "\nFF\nA"
    , grammar: function(l) { with (l) {
        return token(repeat(5, alt(Language.Base.Whitespace(), Language.Base.UpperCase(), Language.Base.LowerCase())));
      }}
    , result: "\nFF\nA"}
  , { input: "\nFF\nA"
    , grammar: function(l) { with (l) {
        return token(repeatBetween(0, 5, alt(Language.Base.Whitespace(), Language.Base.UpperCase(), Language.Base.LowerCase())));
      }}
    , result: "\nFF\nA"}
  , { input: "aaaa"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function fooLR() { with (l) {
          return project(
            either(
              both( fooLR(), any('a') )
            , any('a'))
          , function projectFooLR() {
              this.result = this.result.isArray ? this.result : new Array(this.result);
            })}});
        return fooLR();
      }}
    , result: "[[[[a], a], a], a]"}
  , { input: "abc"
    , grammar: function(l) { with (l) {
        return project(
          bind(
            "foobar"
          , token(both(
              any('a')
            , any('bc')
            ))
          )
        , function() {
            this.result = { frobfrob: this.getVar("foobar") };
          })}}
    , result: "{ frobfrob: \"abc\" }"}
  , { input: " a\nb"
    , grammar: function(l) { with (l) {
        Interleave(function Ignore() { with (l) { return Language.Base.Whitespace() }});
        return iseq(any(), any());
      }}
    , result: "[a, b]"}
  , { input: " a\nb\nc"
    , grammar: function(l) { with (l) {
        Interleave(function Ignore() { with (l) { return Language.Base.Whitespace() }});
        return token(iseq(any('a'), any('b'), any('c')));
      }}
    , result: "abc"}
  , { input: " a\nc"
    , grammar: function(l) { with (l) {
        Interleave(function Ignore() { with (l) { return Language.Base.Whitespace() }});
        return token(both(opil(any('a')), opil(any('c'))));
      }}
    , result: "ac"}
  , { input: " ab\nc"
    , grammar: function(l) { with (l) {
        Interleave(function Ignore() { with (l) { return Language.Base.Whitespace() }});
        return token(oiseq(any('a'), any('b'), any('c')));
      }}
    , result: "abc"}
  , { input: "abxc"
    , grammar: function(l) { with (l) {
        Interleave(function Ignore() { with (l) { return any('x') }});
        return token(oiseq(any('a'), any('b'), any('c')));
      }}
    , result: "abc"}/*
  , { input: ""
    , grammar: function() {
        Module(function Common1(l) {
          l.__Language(function Base(l) {
            l.baz = function(){ return "baz" };
          }).Export();
        }, true);
        Module(function e1__e22(l) {
          l.__Import('e1.e11');
          l.__Language(function foo(l) { with (l.__private) {
            l.Main = function(){ return "e22foo " + Common1.Base.baz() };
          }}).Export();
        });
        Module(function e1__e11(l) {
          l.__Import('e1.e22');
          l.__Language(function foo(l) { with (l.__private) {
            l.Main = function(){ return "foo " + Common1.Base.baz() + ' ' + bar.Main() };
          }}).Export();
          l.__Language(function bar(l) { with (l.__private) {
            l.Main = function(){ return "bar " + blip.blah() + ' ' + e1.e22.foo.Main() };
          }}).Export();
          l.__Language(function blip(l) { with (l.__private) {
            l.blah = function(){ return "blip.blah" };
          }});
        });
        return Module.Exec(function runit(l) {
          l.__Import('e1.e11');
          l.baf = function(){ return "baf" };
          var result = l.baf() + l.e11.foo.Main() + l.e1.e11.bar.Main();
          if ( result == 'baffoo baz bar blip.blah e22foo bazbar blip.blah e22foo baz') {
            return function() { return l.empty() };
          } else {
            return function() { return l.fail() };
          }
        });
      }
    , result: ""}
  , { input: ""
    , grammar: function() {
        Module(function Common1(l) {
          l.__Language(function Base(l) {
            l.baz = function(){ return "baz" };
          }).Export();
        }, true);
        Module(function e1__e22(l) {
          l.__Import('e1.e11');
          l.__Language(function foo(l) { with (l.__private) {
            l.Main = function(){ return "e22foo " + Common1.Base.baz() };
          }}).Export();
        });
        Module(function e1__e11(l) {
          l.__Import('e1.e22');
          l.__Language(function foo(l) { with (l.__private) {
            l.Main = function(){ return "foo " + Common1.Base.baz() + ' ' + bar.Main() };
          }}).Export();
          l.__Language(function bar(l) { with (l.__private) {
            l.Main = function(){ return "bar " + blip.blah() + ' ' + e1.e22.foo.Main() };
          }}).Export();
          l.__Language(function blip(l) { with (l.__private) {
            l.blah = function(){ return "blip.blah" };
          }});
        });
        return Module.Exec(function runit(l) {
          l.__Import('e1.e11');
          l.baf = function(){ return "baf" };
          try { // this is testing whether e11.blip is actually inaccessible due to being not exported.
            l.e11.blip.blah();
            return function() { return l.fail() };
          } catch(e) {
            return function() { return l.empty() };
          }
        });
      }
    , result: ""}
  , { input: ""
    , grammar: function() {
        Module(function e1__e22(l) {
          l.__Language(function foo(l) { with (l.__private) {
            l.Main = function(){ return "BlahBlahfoo" };
          }}).Export();
        });
        return Module.Exec(function runit(l) { with (l) {
          __Import('e1.e22', 'BlahBlah');
          var result = BlahBlah.foo.Main();
          if ( result == 'BlahBlahfoo') {
            return function() { return l.empty() };
          } else {
            return function() { return l.fail() };
          }
        }});
      }
    , result: ""}*/
  , { input: "aaa"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function ListOf(hasArity1) { with (l) {
          var A = this.args[0];
          return either(
            project(
              both(
                bind("list", ListOf(A.reactivate()))
              , bind("a", A.reactivate()))
            , function() { this.result
              = l.array(
                  l.valuesof(this.getVar("list"))
                , this.getVar("a")) })
          , project(
              bind("a", A.reactivate())
            , function() { this.result = [this.getVar("a")] })
          );
        }});
        return ListOf(any("a"));
      }}
    , result: "[a, a, a]"}
  , { input: "aaaaaa"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function ListOf() { with (l) {
          var A = this.args[0];
          var toReturn = either(
            project(
              both(
                bind("list", ListOf(A.reactivate()))
              , bind("a", A.reactivate()))
            , function() { this.result
              = [ this.getVar("list"), this.getVar("a")] })
          , project(
              A.reactivate()
            , function() { this.result = [this.result] })
          );
          return toReturn;
        }});
        return ListOf(any("aa"));
      }}
    , result: "[[[aa], aa], aa]"}
  , { input: "aabzz"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function CharRange(hasArity2) { with (l) {
          var lower = this.args[0];
          var upper = this.args[1];
          return charRange(lower, upper);
        }});
        Func.Rule(l, function CharRange(hasArity1) { with (l) {
          var lower = this.args[0];
          return charRange(lower, '\u00FFFFFF');
        }});
        return token(plus(either(CharRange('a', 'b'), CharRange('z'))));
      }}
    , result: "aabzz"}
  , { input: "a".times(2)
    , grammar: function(l) {
        l.Interleave(function Ignored() { return l.Language.Base.Whitespace() });
        return l.token( l.plus( l.opil( l.both( l.any('a'), l.opt( l.any(';') )))));
      }
    , result: "a".times(2)}
  , { input: "zzzfz"
    , grammar: function(l) { return l.token(l.repeat(5,l.none('qr'))) }
    , result: "zzzfz"}
  , { input: "iiiii"
    , grammar: function(l) { return l.token(l.repeatBetween(0,5,l.any())) }
    , result: "iiiii"}
  , { input: "\nA"
    , grammar: function(l) { with (l) {
        return l.token(repeatBetween(0, 5, alt(Language.Base.Whitespace(), Language.Base.UpperCase(), Language.Base.LowerCase())));
      }}
    , result: "\nA"}
  , { input: "ABBA"
    , grammar: function() {
        Module(function Common(l) {
          l.__Language(function Base(l) { with(l) {
            Func.Rule(l, function LetterA(){ return any("A"); }, undefined, undefined, undefined, 'Common.Base.LetterA');
          }}).Export();
        }, true);
        Module(function Demo(l) {
          l.__Import('DemoLibrary');
          l.__Language(function Lang(l) { with (l) {
            Func.Rule(l, function Main() { return token(plus(either(Common.Base.LetterA(), DemoLibrary.Lang.LetterB()))) });
            Func.Rule(l, function LetterB() { return any("B"); }, undefined, undefined, undefined, 'Demo.Lang.LetterB');
          }}).Export();
        });
        Module(function DemoLibrary(l) {
          l.__Import('Demo');
          l.__Language(function Lang(l) { with (l) {
            Func.Rule(l, function LetterB() { return Demo.Lang.LetterB() }, undefined, undefined, undefined, 'DemoLibrary.Lang.LetterB');
          }}).Export();
        });
        return Module.Exec(function runit(l) { with (l) {
          l.__Import('Demo');
          return [Demo.Lang.Main(), l];
        }});
      }
    , result: "ABBA"}
  , { input: "abc"
    , grammar: function(l) { return l.token(l.either(l.any('a'), l.any('abc'))); }
    , result: "abc"}
  , { input: "abc"
    , grammar: function(l) { return l.token(l.both(l.token(l.either(l.any('a'), l.any('abc'))), l.any('bc'))); }
    , result: "abc"}
  , { input: "abc"
    , grammar: function(l) { return l.token(l.both(l.token(l.either(l.any('a'), l.any('abc'))), l.any('abc'))); }
    , result: "Expected \"abc\" at line 1, column 4; instead found the end of input"}
  , { input: "abca"
    , todo: true // need to add backtracking to left recursive productions *properly* (not exhaustive production as previously)
    , grammar: function(l) { with (l) {
        Func.Rule(l, function fooLR() { with (l) {
          return token( either(
            both( fooLR(), l.token(l.either(l.any('a'), l.any('abc'))) )
          , l.token(l.either(l.any('a'), l.any('abc')))))}});
        return fooLR();
      }}
    , result: "abca"}
  , { input: "abcabcaabc"
    , todo: true
    , grammar: function(l) { with (l) { // local max munch - left-recursive tokens!
        Func.Rule(l, function fooLR() { with (l) {
          return token(either(
            both( fooLR(), l.token(l.either(l.any('a'), l.any('abc'))) )
          , l.token(l.either(l.any('a'), l.any('abc')))))}});
        return fooLR();
      }}
    , result: "abcabcaabc"}
  , { input: "aaaaaaaaaaaaaaaaa"
    , grammar: function(l) {
        return l.regexp('a+');
      }
    , result: "aaaaaaaaaaaaaaaaa"}
  , { input: "aaaaaaaaaaaaaaaaabbbbbbbbbb"
    , grammar: function(l) {
        return l.token(l.both(l.regexp('a+'), l.regexp('b+')));
      }
    , result: "aaaaaaaaaaaaaaaaabbbbbbbbbb"}
  , { input: "aaaaaaaaaaaaaaaaabbbbbbbbbb"
    , grammar: function(l) {
        return l.token(l.seq(l.regexp('a+'), l.regexpE('b?'), l.regexp('b+')));
      }
    , result: "aaaaaaaaaaaaaaaaabbbbbbbbbb"}
  , { input: "aaaaaaaaaaaaaaaaabbbbbbbbbb"
    , grammar: function(l) {
        return l.token(l.seq(l.regexp('a+'), l.regexpE('z?'), l.regexp('b+')));
      }
    , result: "aaaaaaaaaaaaaaaaabbbbbbbbbb"}
  , { input: "blah//blahblah\nblah"
    , grammar: function(l) {
        return l.token(l.seq(l.star(l.any()), l.Language.Grammar.CommentLine(), l.star(l.any())));
      }
    , result: "blah//blahblah\nblah"}
  , { input: "blah/*blahblah*/nblah"
    , grammar: function(l) {
        return l.token(l.seq(l.star(l.any()), l.Language.Grammar.CommentDelimited(), l.star(l.any())));
      }
    , result: "blah/*blahblah*/nblah"}
  , { input: "/*blahblah*/   "
    , grammar: function(l) {
        return l.regexp('(?:(?:(?:[\\u0009\\u000B\\u000C\\u0020\\u000A\\u000D\\u0085\\u2028\\u2029]|\\u000D\\u000A)+|(?:\/\\*(?:[^*]|\\*[^\/])*\\*?\\*\/)|(?:\/\/([^\\n])*(\\u000D\\u000A|[\\u000A\\u000D\\u0085\\u2028\\u2029]|$)))+)');
      }
    , result: "/*blahblah*/   "}
  , { input: "FooFooBlah"
    , grammar: function(l) {
        return l.regexp('[A-Za-z][A-Za-z0-9]*');
      }
    , result: "FooFooBlah"}
    /*
  , { input: " "
    , grammar: function(l) {
        Module.Exec(function runit(l) { with (l) {
          l._gP = {};
          var textLiteral = l.Language.Grammar.TextLiteral();
          print(textLiteral.computeRecogRE(l));
        }});
        return l.regexp('()');
      }
    , result: " "}*/
  , { input: "hi"
    , grammar: function(l) {
        l.Interleave(function() { return l.regexp('\\s') });
        return l.oiplusSep(l.any(','), l.regexp('hi'));
      }
    , result: "[hi]"}
  , { input: "hi , hi, hi ,\nhi"
    , grammar: function(l) {
        l.Interleave(function() { return l.regexp('\\s') });
        return l.oiplusSep(l.any(','), l.regexp('hi'));
      }
    , result: "[hi, hi, hi, hi]"}
  , { input: "hi , hi, hi ,hi"
    , grammar: function(l) {
        l.Interleave(function() { return l.any(' ') });
        return l.oiplusSep(l.any(','), l.regexp('hi'));
      }
    , result: "[hi, hi, hi, hi]"}
  , { input: "aaaaa()"
    , grammar: function(l) {
        return l.token(l.seq(l.regexp('a+'), l.notAfter('\\s'), l.any('()')));
      }
    , result: "aaaaa()"}
  , { input: "aaaaa()"
    , grammar: function(l) {
        return l.token(l.seq(l.regexp('a+'), l.notAfter('\\w'), l.any('()')));
      }
    , result: "Expected notAfter( \"\\\\w\" ) at line 1, column 6; instead found \"()\""}
  , { input: "abcaaaa"
    , grammar: function(l) {
        return l.token(l.plus(l.charRange('a', 'c')));
      }
    , result: "abcaaaa"}
  , { input: "abcd"
    , grammar: function(l) {
        return l.token(l.plus(l.charRange('a', 'c')));
      }
    , result: "Expected charRange( \"a\", \"c\" ) at line 1, column 4; instead found \"d\""}
  , { input: "abcd"
    , grammar: function(l) {
        return l.token(l.both(l.plus(l.either(l.both(l.any(), l.any()), l.any())), l.any()));
      }
    , result: "abcd"}
  , { input: "abcdeaa"
    , grammar: function(l) {
        return l.token(l.both(l.both(l.plus(l.either(l.both(l.any(), l.any()), l.any())), l.any('e')), l.any('aa')));
      }
    , result: "abcdeaa"}
  , { input: "abcde"
    , grammar: function(l) {
        return l.token(l.plus(l.either(l.both(l.any(), l.any()), l.any()), l.any()));
      }
    , result: "abcde"}
  , { input: "'a'|'a'"
    , grammar: function(l) {
        return l.oiplusSep(l.any('|'), l.oiplus(l.any("'a'")));
      }
    , result: "['a', 'a']"}
  , { input: "aab"
    , grammar: function(l) {
        Func.Rule(l, function Foo() { // Foo with arity 1 has sole arg named Param0
          return l.both(l.any('a'), l.resolveByName('Param0'.withScope(l), []));
        }, undefined, undefined, ['Param0']);
        Func.Rule(l, function Foo() { // Foo with arity 0 has no args
          return l.any('b');
        });
        Func.Rule(l, function Foo() { // Foo with arity 2 has args named Param0, Param1
          return l.both(l.resolveByName('Param0'.withScope(l), []), l.resolveByName('Param1'.withScope(l), []));
        }, undefined, undefined, ['Param0', 'Param1']);
        return l.token(l.Foo(l.Foo('a', 'b')));
      }
    , result: "aab"}
  , { input: "abc"
    , grammar: function(l) {
        Func.Rule(l, function AnyInput() {
          return l.tokenizeDefault(l.resolveByName('Param0'.withScope(l), []));
        }, undefined, undefined, ['Param0']);
        Func.Rule(l, function Blah() {
          return l.both(
            l.resolveByName('AnyInput'.withScope(l), [
              function(l){ return l.resolveByName('plus'.withScope(l), [
                function(l){ return l.resolveByName('any'.withScope(l), []) }]) }])
          , l.resolveByName('AnyInput'.withScope(l), [
              function(l){ return l.repeat(
                2
              , l.resolveByName('any'.withScope(l), [])) }]));
        });
        return l.Blah();
      }
    , result: "[a, bc]"}
  , { input: "ab"
    , grammar: function(l) {
        Func.Rule(l, function Foo() { // Foo with arity 1 has sole arg named Param0
          return l.both(l.any('a'), l.resolveByName('Param0'.withScope(l), []));
        }, undefined, undefined, ['Param0']);
        Func.Rule(l, function Foo() { // Foo with arity 0 has no args
          return l.any('b');
        });
        Func.Rule(l, function Foo() { // Foo with arity 2 has args named Param0, Param1
          return l.both(l.resolveByName('Param0'.withScope(l), []), l.resolveByName('Param1'.withScope(l), []));
        }, undefined, undefined, ['Param0', 'Param1']);
        return l.token(l.Foo(l.Foo));
      }
    , result: "ab"}
  , { input: " ab"
    , grammar: function(l) {
        l.Interleave(function(){ return l.resolveByName('Language.Base.Whitespace'.withScope(l), []); });
        Func.Rule(l, function Foo() { // Foo with arity 1 has sole arg named Param0
          return l.both(l.any('a'), l.resolveByName('Param0'.withScope(l), []));
        }, undefined, undefined, ['Param0']);
        Func.Rule(l, function Foo() { // Foo with arity 0 has no args
          return l.any('b');
        });
        Func.Rule(l, function Foo() { // Foo with arity 2 has args named Param0, Param1
          return l.both(l.resolveByName('Param0'.withScope(l), []), l.resolveByName('Param1'.withScope(l), []));
        }, undefined, undefined, ['Param0', 'Param1']);
        return l.token(l.syntax(l.Foo(l.Foo)));
      }
    , result: "ab"}
  , { input: "a9b"
    , grammar: function(l) {
        Func.Rule(l, function Letter() {
          return l.either(l.charRange('A','Z'),l.charRange('a','z'))
        });
        Func.Rule(l, function Digit() {
          return l.charRange('0','9');
        });
        Func.Rule(l, function Foo() {
          return l.both(l.resolveByName('Letter'.withScope(l), [])
          , l.star(l.either(l.resolveByName('Letter'.withScope(l), [])
          , l.resolveByName('Digit'.withScope(l), []))));
        });
        return l.token(l.Foo());
      }
    , result: "a9b"}
  , { input: "=a"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function Atom() {
          return either(
            useFirst(both(
              useSecond(both(opil(any('(')), opil(l.resolveByName('Atom'.withScope(l), []))))
            , opil(any(')'))))
          , opil(Expression()));
        });
        Func.Rule(l, function Expression() {
          return either(Addition(), any('a'));
        });
        Func.Rule(l, function Addition() {
          return both(Atom(), any('z'));
        });
        return token(both(any('='), Atom()));
      }}
    , result: "=a"}
  , { input: "=a-a"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function Atom() {
          return either(
            alt(any('a'), Infix(any("+")), Infix(any("-")))
          , useFirst(both(
              useSecond(both(any('('), l.Atom()))
            , any(')'))));
        });
        
        Func.Rule(l, function Infix() {
          return seq(
            bind("leftOperand", l.Atom())
          , this.args[0].reactivate(this)
          , bind("rightOperand", l.Atom()));
        });
        
        return token(both(any('='), Atom()));
      }}
    , result: "=a-a"}
  , { input: "=(a+a)*a"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function Atom() {
          return either(
            useFirst(both(
              useSecond(both(opil(any('(')), opil(l.resolveByName('Atom'.withScope(l), []))))
            , opil(any(')'))))
          , opil(Expression()));
        });
        
        Func.Rule(l, function Expression() {
          return alt(Addition(), Multiplication(), any('a'));
        });
        
        Func.Rule(l, function Infix() {
          var operation = this.args[1];
          return syntax(
              bind("leftOperand", Atom())
            , this.args[0].reactivate(this)
            , bind("rightOperand", Atom()));
        });
        
        Func.Rule(l, function Multiplication() {
          return Infix(any("*"), function() {
            this.result = this.first.result*1 * this.second.result*1;
          });
        });
        
        Func.Rule(l, function Addition() {
          return Infix(any("+"), function() {
            this.result = this.first.result*1 + this.second.result*1;
          });
        });
        
        return token(both(any('='), Expression()));
      }}
    , result: "=a+a*a"}
  , { input: "=a+a*a"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function Atom() {
          return either(
            useFirst(both(
              useSecond(both(opil(any('(')), opil(l.resolveByName('Atom'.withScope(l), []))))
            , opil(any(')'))))
          , opil(Expression()));
        });
        
        Func.Rule(l, function Expression() {
          return alt(Addition(), Multiplication(), any('a'));
        });
        
        Func.Rule(l, function Infix() {
          var operation = this.args[1];
          return syntax(
              bind("leftOperand", Atom())
            , this.args[0].reactivate(this)
            , bind("rightOperand", Atom()));
        });
        
        Func.Rule(l, function Multiplication() {
          return Infix(any("*"), function() {
            this.result = this.first.result*1 * this.second.result*1;
          });
        });
        
        Func.Rule(l, function Addition() {
          return Infix(any("+"), function() {
            this.result = this.first.result*1 + this.second.result*1;
          });
        });
        
        return token(both(any('='), Expression()));
      }}
    , result: "=a+a*a"}
  , { input: "bbba"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function Bloop() {
          return either(
            this.args[0].reactivate(this)
          , both(
              any("b")
            , resolveByName("Bloop".withScope(l), [this.args[0].reactivate(this)])));
        });
        
        return token(resolveByName("Bloop".withScope(l), [any('a')]));
      }}
    , result: "bbba"}
  , { input: "bba"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function Bloop() {
          return either(
            resolveByName('Param0'.withScope(l), [])
          , both(
              resolveByName("Bloop".withScope(l), [any("b")])
            , resolveByName('Param0'.withScope(l), [])));
        }, undefined, undefined, ['Param0']);
        
        return token(resolveByName("Bloop".withScope(l), [any('a')]));
      }}
    , result: "bba"}
  , { input: "bbba"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function Bloop() {
          return either(
            resolveByName('Param0'.withScope(l), [])
          , both(
              resolveByName("Bloop".withScope(l), [any("b")])
            , resolveByName('Param0'.withScope(l), [])));
        }, undefined, undefined, ['Param0']);
        
        return token(resolveByName("Bloop".withScope(l), [any('a')]));
      }}
    , result: "bbba"}
  , { input: "b"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function Bloop() {
          return either(
            resolveByName('Param0'.withScope(l), [])
          , both(
              resolveByName("Bloop".withScope(l), [any("b")])
            , resolveByName('Param0'.withScope(l), [])));
        }, undefined, undefined, ['Param0']);
        
        return token(resolveByName("Bloop".withScope(l), [any('a')]));
      }}
    , result: "Expected \"b\" at line 1, column 2; instead found the end of input"}
  , { input: "a"
    , grammar: function(l) { with (l) {
        Func.Rule(l, function Foo() {
          return project(regexp("a"), function() { this.result = "blah" });
        });
        
        return Foo();
      }}
    , result: "blah"}
  , { input: "abc"
    , grammar: function(l) {
        inNestedParse = true;
        Module(function Microsoft__M__Grammar(l) {
          l.__Language(MGrammar).Export();
        });
        return Module.Exec(function runit(l) {
          l.__Import('Microsoft.M.Grammar');
          l.__Init(["\
module JSMeta {\
  language HelloWorld {\
    syntax Main = AnyInput(any any any | any any | any) AnyInput(any any);\
    token AnyInput(Param0) = res:Param0 => res;\
  }\
}",
"\
module JSMeta {\
  language HelloWorld {\
    syntax Main = AnyInput(any+) AnyInput(any any);\
    token AnyInput(Param0) = res:Param0 => res;\
  }\
}"][1]);
          return [l.Microsoft.M.Grammar.MGrammar.Main(), l];
        });}
    , result: '{ Main: [ "a", "bc" ] }'}
  , testMGrammarGrammarAndInput("\
module JSMeta {\
  language HelloWorld {\
    syntax Main = Foo;\
    token Foo = res:(any any+) => res;\
  }\
}", "abcde", '{ Main: "abcde" }')
  , testMGrammarGrammarAndInput("\
module JSMeta {\
  language HelloWorld {\
    syntax Main = AnyInput(any);\
    token AnyInput(Param0) = (res:Param0*) => res;\
  }\
}", "abcde", 'Parsing problem: failed to resolve res at line 1, column 6')
  , testMGrammarGrammarAndInput("\
module JSMeta {\
  language HelloWorld {\
    syntax Main = AnyInput(any);\
    token AnyInput(Param0) = res:Param0* => res;\
  }\
}", "abcde", '{ Main: [ "a", "b", "c", "d", "e" ] }')
  ];
  
  //try {
  var r;
  var onlyTest = 0
    //  + 90;
  
  if (onlyTest) {
    debugMode = traceOn = true;
  }
  
  //if (!inConsole) traceOn = true;
  
  var minTests = 0;
  var maxTests = 999;
  var testsTried = 0;
  var i = 0;
  
  for (i = minTests; i < tests.length && i <= maxTests; i++) {
    if (tests[i].skip || tests[i].todo || (onlyTest && i!=onlyTest)) continue;
    testsTried++;
    if (/^Expected\s/.test(tests[i].result)) {
      ignoreDisqualifyingREs = true;
    }
    inNestedParse = false;
    r = Module.Parse(tests[i].grammar, (JSMetaPadInputString = tests[i].input));
    if (inConsole) {
      //Print([i,r]);
    }
    if ((r.result!=tests[i].result.toString() && r.errorMsg!=tests[i].result.toString()) && !traceOn && !inConsole) {
      //ignoreDisqualifyingREs = 
      traceOn = true;
      inNestedParse = false;
      r = Module.Parse(tests[i].grammar, tests[i].input);
      //ignoreDisqualifyingREs = 
      traceOn = false;
      result += '\n' + ((r.result==tests[i].result.toString() || r.errorMsg==tests[i].result.toString()) ? 'ok '+i+' ...'+[r.duration, r.activations, r.reTests, r.grammar] : 'nok '+i + '...'+[r.duration, r.activations, r.reTests, r.grammar, r.errorMsg.toString(), r.result.toString(), tests[i].result.toString()]);
      break;
    } else {
      result += '\n' + ((r.result==tests[i].result.toString() || r.errorMsg==tests[i].result.toString()) ? 'ok '+i+' ...'+[r.duration, r.activations, r.reTests, r.grammar] : 'nok '+i + '...'+[r.duration, r.activations, r.reTests, r.grammar, r.errorMsg.toString(), r.result.toString(), tests[i].result.toString()]);
    }
    ignoreDisqualifyingREs = false;
  }
  
  if (!onlyTest) {
  
  testsTried = Math.max(testsTried, i-1);
  
  // some cheezy repetition/activation performance tests.
  
  var TestReps = 6;
  if (maxTests != 999) TestReps = 0;
  
  //if (typeof(console)!='undefined' && console.profile) { console.profile() }
  for (var i = 0; i < TestReps; i++) {
    var leng = Math.pow(2, i);
    var gg = function(l) { return l.token(l.repeat(leng, l.any())) };
    testsTried++; tests.length++;
    try { r = Module.Parse(gg, "i".times(leng)); } catch (e) { r = { outcome: false}; }
    r.len = leng;
    result += '\n' + (r.result.length==leng ? '' : 'n') + 'ok '+testsTried+' ...'+[r.duration, r.outcome, r.activations, r.reTests, r.grammar, r.len];
    if (!r.outcome && !traceOn && !inConsole) {
      ignoreDisqualifyingREs = traceOn = true;
      Module.Parse(gg, "i".times(leng));
      ignoreDisqualifyingREs = traceOn = false;
      break;
    }
  }
  
  TestReps = 6;
  if (maxTests != 999) TestReps = 0;
  
  //if (typeof(console)!='undefined' && console.profile) { console.profile() }
  for (var i = 0; i < TestReps; i++) {
    var leng = Math.pow(2, i);
    var gg = function(l) {
      Func.Rule(l, function T() {
        return l.either( l.both( l.plus( l.any('i')), l.any('f')), l.any('i') )
      });
      return l.token(l.plus(l.T()));
    };
    testsTried++; tests.length++;
    try { r = Module.Parse(gg, "i".times(leng)); } catch (e) { r = { outcome: false}; }
    r.len = leng;
    result += '\n' + (r.result.length==leng ? '' : 'n') + 'ok '+testsTried+' ...'+[r.duration, r.outcome, r.activations, r.reTests, r.grammar, r.len];
    if (!r.outcome && !traceOn && !inConsole) {
      ignoreDisqualifyingREs = traceOn = true;
      Module.Parse(gg, "i".times(leng));
      ignoreDisqualifyingREs = traceOn = false;
      break;
    }
  }
  
  }
  
  result += (inConsole ? '\n\n\t\t' : '<br /><br />&nbsp&nbsp;&nbsp;') + ' Test Duration: ' + ((new Date()).getTime() - startTime) + ' ms   Tests Tried: ' + testsTried + ' of ' + tests.length + '\n\t\t\tActivations: ' + Func.activations() + '\n\t\t\tre tests   : '+ Func._reTests();
  
  //} catch(e) { result += (inConsole ? '\n   ' : '<br />&nbsp&nbsp;&nbsp;') + e; }
  
  //if (typeof(console)!='undefined' && console.profile) { console.profileEnd() }
  return result;
};
























